#include <os/driver.h>
#include <os/debug.h>
#include <os/initcall.h>
#include <os/hardirq.h>
#include <lib/type.h>
#include <lib/assert.h>
#include <lib/unistd.h>
#include <lib/assert.h>
#include <sys/input.h>
#include <sys/ioctl.h>
#include <sys/res.h>
#include <driver/mouse.h>
#include <arch/x86.h>


//#define DEBUG_MOUSE

static int InitController();
static int TestController();
static void OpenFirstPort();
static void OpenSecondPort();
static void SendCmd(uint8_t cmd);
static uint8_t CheckDeviceType(uint8_t port);
static int CheckOutBuffer();
static uint8_t ReadData();
static uint8_t ReadStatus();
static void WriteData(uint8_t data);
static void WriteDataTo(uint8_t data, uint8_t port);
static int CheckInBuffer();
static void WaitInBuffer();
static void WaitOutBuffer();
static inline int TestPort(uint8_t id);
static inline void CheckAck();

static void MouseInit(device_extension_t *extension);
static int MouseProbe(device_extension_t *extension, int port);
static int MouseHandler(irqno_t irq, void *data);
static void MouseCommitPacket(device_extension_t *extension);
static int MouseParse(device_extension_t *extension);

// return device type
// type: 1 mouse
//       0 keyboard
static uint8_t CheckDeviceType(uint8_t port)
{
    uint8_t data;
    uint8_t identify0;
    uint8_t identify1;
    uint8_t type = PS2_TYPE_NONE;

    // send cmd get identify from device
    WriteDataTo(PS2_DEVICE_CMD_IDENTIFY, port);
    // wait device return ACK
    data = ReadData();
    if (data == PS2_DEVICE_CMD_RETURN_ACK)
    {
        // receive identify byte from device
        int timeout = 1000;
        // identify 0
        while (timeout-- && CheckInBuffer())
            ;
        if (timeout == 0)
        {
            identify0 = 0xff;
        }
        else
        {
            identify0 = In8(PS2CTRL_IO_DATA_PORT);
        }
        // identify1
        timeout = 1000;
        while (timeout-- && CheckInBuffer())
            ;
        if (timeout == 0)
        {
            identify1 = 0xff;
        }
        else
        {
            identify1 = In8(PS2CTRL_IO_DATA_PORT);
        }

        // if identify is any mouse type
        if (identify0 == 0x00 || identify0 == 0x03 || identify0 == 0x04)
        {
            type = PS2_TYPE_MOUSE;
        }
        else
        {
            // check if is keyboard type device
            if ((identify0 == 0xAB && identify1 == 0x41) || identify0 == 0xAB && identify1 == 0xC1 || identify0 == 0xAB && identify1 == 0x83)
                type = PS2_TYPE_KEYBOARD;
            else
                type = PS2_TYPE_KEYBOARD;
        }
    }

    return type;
}

// read data from ps2 controller data port
static inline uint8_t ReadData()
{
    uint8_t data;

    // check output buffer
    WaitOutBuffer();
    // read data
    data = In8(PS2CTRL_IO_DATA_PORT);
    return data;
}

static inline void SendCmd(uint8_t cmd)
{
    // wait input buffer empty
    WaitInBuffer();
    // send cmd
    Out8(PS2CTRL_IO_CMD_PORT, cmd);
}

// wait in buffer empty
static inline void WaitInBuffer()
{
    uint8_t data;
    while ((data = In8(PS2CTRL_IO_STATUS_PORT)) & PS2CTRL_STATUS_INFULL)
        ;
}

// wait out buffer full
static inline void WaitOutBuffer()
{
    uint8_t data;
    while (!((data = In8(PS2CTRL_IO_STATUS_PORT)) & PS2CTRL_STATUS_OUTFULL))
        ;
}

static void WriteData(uint8_t data)
{
    // wait input buffer empty
    WaitInBuffer();
    // send data to second ps/2 port
    Out8(PS2CTRL_IO_DATA_PORT, data);
}

static void WriteDataTo(uint8_t data, uint8_t port)
{
    uint8_t flag, count;
    // first ps/2 port
    if (port == 0x00)
    {
        WaitInBuffer();
        // send data to data port
        Out8(PS2CTRL_IO_DATA_PORT, data);
    }
    // second ps/2 port
    // we need first send a cmd to enable second port
    else
    {
        if (port = 0x01)
        {
            // send cmd to write to second portr
            SendCmd(PS2CTRL_CMD_WRITENEXTBYTE_TO_SECONDPORTINBUFF);
            WaitInBuffer();
            // send data to second ps/2 port
            Out8(PS2CTRL_IO_DATA_PORT, data);
        }
    }
}

// check input buffer
static int CheckInBuffer()
{
    uint8_t data;
    // read status
    data = In8(PS2CTRL_IO_STATUS_PORT);
    if (data & PS2CTRL_STATUS_INFULL)
    {
        return 1;
    }
    return 0;
}

static uint8_t ReadStatus()
{
    return In8(PS2CTRL_IO_STATUS_PORT);
}

// check output buffer
static int CheckOutBuffer()
{
    uint8_t flag, status;
    // read controller status
    status = In8(PS2CTRL_IO_STATUS_PORT);
    // test status flags
    if (status & PS2CTRL_STATUS_OUTFULL) // no is empty
        return 1;

    return 0;
}

static void FlushBuffer()
{
    uint8_t data;
    // if output buffer no empty
    while (CheckOutBuffer())
    {
        data = In8(PS2CTRL_IO_DATA_PORT);
    }
}

static uint8_t ReadConfig()
{
    uint8_t config;

    // send read config bytes cmd
    SendCmd(PS2CTRL_CMD_READCONFIGBYTE);
    // wait and receive data
    config = ReadData();
    return config;
}

static inline void CheckAck()
{
    while ((ReadData()) != MOUSE_RET_ACK)
        ;
}

static void WriteConfig(uint8_t config)
{
    uint8_t temp;
    SendCmd(PS2CTRL_CMD_READCONFIGBYTE);
    temp = ReadData();
    // send write config space cmd
    SendCmd(PS2CTRL_CMD_WRITECONFIGBYTE);
    // write config data to data port
    WriteData(temp | config);
}

static iostatus_t MouseEnter(driver_object_t *driver)
{
    iostatus_t status = IO_SUCCESS;
    device_extension_t *extension;
    uint8_t err = 0;
    device_object_t *device;

    for (int i = 0; i < 2; i++)
    {
        status = IoCreateDevice(driver, sizeof(device_extension_t), DEVICE_NAME, DEVICE_TYPE_MOUSE, &device);
        if (status != IO_SUCCESS)
        {
            KPrint(PRINT_ERR "%s: create device failed!\n", __func__);
            return IO_FAILED;
        }
        // neither io mode
        device->flags = 0;
        extension = device->device_extension;
        extension->devobj = device;
        extension->open = 0;

        // probe device info
        if (MouseProbe(extension, i) < 0)
        {
            err++;
            IoDeleteDevice(device);
            status = IO_FAILED;
            continue; // probe next port
        }

        memset(&extension->raw_data, 0, sizeof(mouse_data_t));
        extension->irq = IRQ12_MOUSE;
        extension->phase = 0;
        extension->seq = 0;
        extension->flags = 0;
        extension->button = 0;

        InputEventInit(&extension->eventbuff);

        // enable interrupt
        WriteConfig(PS2_CONFIG);
        // init mouse device
        MouseInit(extension);

        IrqRegister(extension->irq, MouseHandler, IRQ_DISABLE, "mouse", DRIVER_NAME, extension);
    }

    if (err < 2)
    {
        status = IO_SUCCESS;
    }
    else
    {
        KPrint("[mouse] system no found mouse!\n");
        status = IO_FAILED;
    }

    return status;
}

static inline void MouseCommitPacket(device_extension_t *extension)
{
    extension->phase = 0; // next packet
    // parse mouse data
    MouseParse(extension);
}

static int MouseHandler(irqno_t irq, void *data)
{
    device_extension_t *extension = data;

    while (1)
    {
        uint8_t status = In8(PS2CTRL_IO_STATUS_PORT);
        if (!(status & PS2CTRL_STATUS_OUTFULL) || (status & PS2CTRL_STATUS_WHICHBUFF != PS2CTRL_STATUS_MOUSEBUFF))
            return 0;

        uint8_t data = In8(PS2CTRL_IO_DATA_PORT);
        extension->raw_data[extension->phase] = data;

        switch (extension->phase)
        {
        case 0:
            if (!(data & 0x08))
            {
                KPrint("mouse: stream out of sync\n");
                return 0;
            }
            ++extension->phase;
            break;
        case 1:
            ++extension->phase;
            break;
        case 2:
            if (extension->has_wheel)
            {
                ++extension->phase;
                break;
            }
            MouseCommitPacket(extension);
            return 0;
        case 3:
            assert(extension->has_wheel);
            MouseCommitPacket(extension);
            return 0;
        default:
            KPrint("mouse parse out of packet\n");
            return 0;
        }
    }

    return 0;
}



static int MouseProbe(device_extension_t *extension, int port)
{
    uint8_t type;

    type = CheckDeviceType(port);
    if (type != PS2_TYPE_MOUSE)
        return -1;
    extension->type = type;
    extension->id = port;
    KPrint("[mouse] found mouse on port %d!\n", port);
    return 0;
}

static int MouseParse(device_extension_t *extension)
{
    if (!extension->open)
        return 0;

    input_event_t event;
    uint8_t button;
    int x = extension->raw_data[1];
    int y = extension->raw_data[2];
    int z = (extension->has_wheel) ? extension->raw_data[3] : 0; // with wheel read bytes3
    bool x_overflow = extension->raw_data[0] & 0x40;
    bool y_overflow = extension->raw_data[0] & 0x80;
    bool x_sign = extension->raw_data[0] & 0x10;
    bool y_sign = extension->raw_data[0] & 0x20;

    if (x && x_sign)
    {
        x |= 0xFFFFFF00;
    }
    if (y && y_sign)
    {
        y |= 0xFFFFFF00;
    }
    // overflow
    if (x_overflow || y_overflow)
    {
        x = 0;
        y = 0;
    }

    if (x || y)
    {
        // horizontal director
        if (x) // xrel chanaged
        {
            event.type = EVENT_REL;
            event.code = REL_X;
            event.value = x;
            InputEventPut(&extension->eventbuff, &event);
        }
        // vertical director
        if (y) // yres chanaged
        {
            event.type = EVENT_REL;
            event.code = REL_Y;
            event.value = y;
            InputEventPut(&extension->eventbuff, &event);
        }
        // make sync event
        event.type = EVENT_SYN;
        event.code = 0;
        event.value = 0;
        InputEventPut(&extension->eventbuff, &event);
#ifdef DEBUG_MOUSE
        KPrint("mouse move,x rel %d y rel %d\n", x, y);
#endif
    }

    // depth director
    if (z)
    {
        event.type = EVENT_REL;
        event.code = REL_WHEEL;
        if (z == 1)
            event.value = 1; // up scroll
        else
            event.value = -1; // down scroll

        InputEventPut(&extension->eventbuff, &event);
    }

    button = extension->raw_data[0] & MOUSE_DATA_BUTTON_MASK;
    if (button & 0x01) // left button
    {
#ifdef DEBUG_MOUSE
        KPrint("[mouse] left\n");
#endif
        if (!(extension->button & 0x01))
        {
            extension->button |= 0x01;

            // left button press
            event.type = EVENT_KEY;
            event.code = BTN_LEFT;
            event.value = 1;
            InputEventPut(&extension->eventbuff, &event);
        }
    }
    else
    {
        if (extension->button & 0x01)
        {
            extension->button &= ~0x01;
            event.type = EVENT_KEY;
            event.code = BTN_LEFT;
            event.value = 0;
            InputEventPut(&extension->eventbuff, &event);
        }
    }

    if (button & 0x02) // right button
    {
#ifdef DEBUG_MOUSE
        KPrint("[mouse] right\n");
#endif

        if (!(extension->button))
        {
            extension->button |= 0x02;

            event.type = EVENT_KEY;
            event.code = BTN_RIGHT;
            event.value = 1;
            InputEventPut(&extension->eventbuff, &event);
        }
    }
    else
    {
        // right button no press
        if (extension->button & 0x02)
        {
            extension->button &= ~0x02;

            event.type = EVENT_KEY;
            event.code = BTN_RIGHT;
            event.value = 0;
            InputEventPut(&extension->eventbuff, &event);
        }
    }

    if (button & 0x04) // middle button
    {
#ifdef DEBUG_MOUSE
        KPrint("[mouse] middle\n");
#endif

        if (!(extension->button))
        {
            extension->button |= 0x04;

            event.type = EVENT_KEY;
            event.code = BTN_MIDDLE;
            event.value = 1;
            InputEventPut(&extension->eventbuff, &event);
        }
    }
    else
    {
        if (extension->button & 0x04)
        {
            extension->button &= ~0x04;

            event.type = EVENT_KEY;
            event.code = BTN_MIDDLE;
            event.value = 0;
            InputEventPut(&extension->eventbuff, &event);
        }
    }
}

static void MouseInit(device_extension_t *extension)
{
    uint8_t devid;

    KPrint("[mouse] init mouse device\n");

    // reset mouse
    WriteDataTo(MOUSE_CMD_RESET, extension->id);
    CheckAck();
    // set default
    WriteDataTo(MOUSE_CMD_SETDEFALUT, extension->id);
    CheckAck();
    // enable data packet send
    WriteDataTo(MOUSE_CMD_ENABLE_SEND, extension->id);
    CheckAck();
    // get device id
    WriteDataTo(MOUSE_CMD_GETID, extension->id);
    CheckAck();
    devid = ReadData();
    if (devid != MOUSE_INTELLI)
    {
        // magic packet to enable wheel
        WriteDataTo(MOUSE_CMD_SET_RATE, extension->id);
        CheckAck();
        WriteDataTo(200, extension->id);
        CheckAck();
        WriteDataTo(MOUSE_CMD_SET_RATE, extension->id);
        CheckAck();
        WriteDataTo(100, extension->id);
        CheckAck();
        WriteDataTo(MOUSE_CMD_SET_RATE, extension->id);
        CheckAck();
        WriteDataTo(80, extension->id);
        CheckAck();
        WriteDataTo(MOUSE_CMD_GETID, extension->id);
        CheckAck();
        devid = ReadData();
    }
    // wheel set
    if (devid == MOUSE_INTELLI)
    {
        extension->has_wheel = 1;
        KPrint("mouse: mouse had wheel!\n");
    }
    else
    {
        KPrint("mouse: mouse had not wheel!\n");
    }
    //set rate
    WriteDataTo(MOUSE_CMD_SET_RATE, extension->id);
    CheckAck();
    WriteDataTo(100, extension->id);
    CheckAck();
}

static iostatus_t MouseOpen(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;

    extension->open = 1;
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t MouseClose(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;

    extension->open = 0;
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t MouseRead(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;
    input_event_t *event = ioreq->user_buff;

    if (ioreq->user_buff && ioreq->parame.read.len)
    {
        if (extension->flags & DEVICE_NOWAIT)
        {
            if (InputEventGet(&extension->eventbuff, event))
            {
                status = IO_FAILED;
            }
        }
        else
        {
            while (1)
            {
                if (!InputEventGet(&extension->eventbuff, event))
                {
                    break;
                }
            }
        }
    }
    else
    {
        status = IO_FAILED;
    }
    ioreq->io_status.status = status;
    ioreq->io_status.info = ioreq->parame.read.len;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t MouseFastRead(device_object_t *device,uint32_t size,void *buff)
{
        device_extension_t *extension=device->device_extension;
        iostatus_t status=IO_SUCCESS;
        input_event_t *event=(input_event_t*)buff;

        if (extension->flags & DEVICE_NOWAIT)
        {
            if (InputEventGet(&extension->eventbuff, event))
            {
                status = IO_FAILED;
            }
        }
        else
        {
            while (1)
            {
                if (!InputEventGet(&extension->eventbuff, event))
                {
                    break;
                }
            }
        }

        return status;
}

static iostatus_t MouseDevCtl(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;
    uint32_t cmd = ioreq->parame.devctl.code;
    uint32_t arg = ioreq->parame.devctl.arg;

    switch (cmd)
    {
    case MOUSEIO_SETFLAGS:
        extension->flags = *(uint32_t *)arg;
        break;
    case MOUSEIO_GETFLAGS:
        *(uint32_t *)arg = extension->flags;
        break;
    default:
        break;
    }
    ioreq->io_status.info = 0;
    ioreq->io_status.status = status;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t MouseExit(driver_object_t *driver)
{
    device_object_t *device, *next;

    list_traversal_all_owner_to_next_safe(device, next, &driver->device_list, list)
    {
        list_del_init(&device->list);
    }
    string_del(&driver->name); // delete driver naem
    return IO_SUCCESS;
}

static iostatus_t MouseDriverFunc(driver_object_t *driver)
{
    iostatus_t status;

    driver->driver_enter = MouseEnter;
    driver->driver_exit = MouseExit;

    driver->dispatch_fun[IOREQ_OPEN] = MouseOpen;
    driver->dispatch_fun[IOREQ_CLOSE] = MouseClose;
    driver->dispatch_fun[IOREQ_READ] = MouseRead;
    driver->dispatch_fun[IOREQ_DEVCTL] = MouseDevCtl;
    driver->dispatch_fun[IOREQ_FASTREAD]=MouseFastRead;

    string_new(&driver->name, DRIVER_NAME, DRIVER_NAME_LEN);

    return IO_SUCCESS;
}

static void __init MouseDriverEntry()
{
    KPrint("[driver] create mouse driver\n");
    if (DriverObjectCreate(MouseDriverFunc) < 0)
    {
        KPrint(PRINT_ERR "[driver]:%s create driver failed!\n", __func__);
    }
}

driver_initcall(MouseDriverEntry);
